require 'jinx/metadata/propertied'
require 'caruby/metadata/property'
require 'caruby/metadata/java_property'

module CaRuby
  # Augments a caRuby application domain class with {PropertyCharacteristics} persistence meta-data.
  #
  # A Jinx-enabled caRuby application domain module enables CaRuby persistence by adding the following
  # method definition:
  #   def self.add_metadata(klass)
  #     super
  #     klass.extend(CaRuby::Propertied)
  #   end
  module Propertied
    include Jinx::Propertied
    
    # @return [<Symbol>] the dependent attributes
    def autogenerated_dependent_attributes
      @ag_dep_flt ||= dependent_attributes.compose { |prop| prop.autogenerated? }
    end

    # @return [<Symbol>] the autogenerated logical dependent attributes
    # @see #logical_dependent_attributes
    # @see Property#autogenerated?
    def autogenerated_logical_dependent_attributes
      @ag_log_dep_flt ||= dependent_attributes.compose { |prop| prop.autogenerated? and prop.logical? }
    end
    
    # @return [<Symbol>] the {Property#fetch_saved?} attributes
    def saved_attributes_to_fetch
      @sv_ftch_flt ||= domain_attributes.compose { |prop| prop.fetch_saved? }
    end
    
    # @return [<Symbol>] the logical dependent attributes
    # @see Property#logical?
    def logical_dependent_attributes
      @log_dep_flt ||= dependent_attributes.compose { |prop| prop.logical? }
    end

    # @return [<Symbol>] the auto-generated attributes
    # @see Property#autogenerated?
    def autogenerated_attributes
      @ag_flt ||= attribute_filter { |prop| prop.autogenerated? }
    end
    
    # @return [<Symbol>] the auto-generated non-domain attributes
    # @see Property#nondomain?
    # @see Property#autogenerated?
    def autogenerated_nondomain_attributes
      @ag_nd_flt ||= attribute_filter { |prop| prop.autogenerated? and prop.nondomain? }
    end
    
    # @return [<Symbol>] the {Property#volatile?} non-domain attributes
    def volatile_nondomain_attributes
      @unsvd_nd_flt ||= attribute_filter { |prop| prop.volatile? and prop.nondomain? }
    end

    # @return [<Symbol>] the domain attributes which can serve as a query parameter
    # @see Property#searchable?
    def searchable_attributes
      @srchbl_flt ||= attribute_filter { |prop| prop.searchable? }
    end

    # @return [<Symbol>] the create/update cascaded domain attributes
    # @see Property#cascaded?
    def cascaded_attributes
      @cscd_flt ||= domain_attributes.compose { |prop| prop.cascaded? }
    end

    # @return [<Symbol>] the {#cascaded_attributes} which are saved with a proxy
    #   using the dependent saver_proxy method
    def proxied_savable_template_attributes
      @px_cscd_flt ||= savable_template_attributes.compose { |prop| prop.proxied_save? }
    end

    # @return [<Symbol>] the {#cascaded_attributes} which do not have a
    #   #{Property#proxied_save?}
    def unproxied_savable_template_attributes
      @unpx_sv_tmpl_flt ||= savable_template_attributes.compose { |prop| not prop.proxied_save? }
    end

    # @return [<Symbol>] the {#domain_attributes} to {Property#include_in_save_template?}
    def savable_template_attributes
      @sv_tmpl_flt ||= domain_attributes.compose { |prop| prop.include_in_save_template? }
    end
    
    # Returns the physical or auto-generated logical dependent attributes that can
    # be copied from a save result to the given save argument object.
    #
    # @return [<Symbol>] the attributes that can be copied from a save result to a
    #  save argument object
    # @see Property#autogenerated?
    def copyable_saved_attributes
      @cp_sv_flt ||= dependent_attributes.compose { |prop| prop.autogenerated? or not prop.logical? }
    end

    # @return [<Symbol>] the attributes which are {Property#creatable?}
    def creatable_attributes
      @cr_flt ||= attribute_filter { |prop| prop.creatable? }
    end

    # @return [<Symbol>] the attributes which are {Property#updatable?}
    def updatable_attributes
      @upd_flt ||= attribute_filter { |prop| prop.updatable? }
    end

    def fetched_dependent_attributes
      @fch_dep_flt ||= (fetched_domain_attributes & dependent_attributes).to_a
    end

    # @return [<Symbol>] the saved dependent attributes
    def saved_dependent_attributes
      @sv_dep_flt ||= attribute_filter { |prop| prop.dependent? and prop.saved? }
    end
    
    # @return [<Symbol>] the saved {Property#independent?} attributes
    # @see Property#saved?
    def saved_independent_attributes
      @sv_ind_flt ||= attribute_filter { |prop| prop.independent? and prop.saved? }
    end

    # @return [<Symbol>] the domain {Property#sync?} attributes
    def sync_domain_attributes
      @sv_sync_dom_flt ||= domain_attributes.compose { |prop| prop.sync? }
    end

    # @return [<Symbol>] the non-domain {Property#saved?} attributes
    def saved_nondomain_attributes
      @sv_ndom_flt ||= nondomain_attributes.compose { |prop| prop.saved? }
    end

    # @return [<Symbol>] the {Property#volatile?} {#nondomain_attributes}
    def volatile_nondomain_attributes
      @vl_ndom_flt ||= nondomain_attributes.compose { |prop| prop.volatile? }
    end

    # @return [<Symbol>] the domain {#creatable_attributes}
    def creatable_domain_attributes
      @cr_dom_flt ||= domain_attributes.compose { |prop| prop.creatable? }
    end

    # @return [<Symbol>] the domain {#updatable_attributes}
    def updatable_domain_attributes
      @upd_dom_flt ||= domain_attributes.compose { |prop| prop.updatable? }
    end

    # @return [<Symbol>] the attributes which are populated from the database
    # @see Property#fetched?
    def fetched_attributes
      @fch_flt ||= attribute_filter { |prop| prop.fetched? }
    end

    # Returns the domain attributes which are populated in a query on the given fetched instance of
    # this metadata's subject class. The domain attribute is fetched if it satisfies the following 
    # conditions:
    # * the attribute is a dependent attribute or of abstract domain type
    # * the attribute is not specified as unfetched in the configuration
    #
    # @return [<Symbol>] the attributes which are {Property#fetched?}
    def fetched_domain_attributes
      @fch_dom_flt ||= domain_attributes.compose { |prop| prop.fetched? }
    end

    #@return [<Symbol>] the #domain_attributes which are not #fetched_domain_attributes
    def unfetched_attributes
      @unftchd_flt ||= domain_attributes.compose { |prop| not prop.fetched? }
    end
    
    alias :toxic_attributes :unfetched_attributes

    # @quirk JRuby - This method body is copied to the +CaTissue::Metadata+ superclass since
    #   calling super from that class's overloaded +loadable_attributes+ method results in
    #   an infinite loop.
    #
    # @return [<Symbol>] the Java attribute non-abstract {#unfetched_attributes}
    def loadable_attributes
      # A change to this method body must be copied to CaTissue::Metadata; see rubydoc above.
      @ld_flt ||= unfetched_attributes.compose do |prop|
        prop.java_property? and not prop.type.abstract? and not prop.transient?
      end
    end
    
    private

    def create_nonjava_property(attribute, type, *flags)
      Property.new(attribute, self, type, *flags)
    end
    
    def create_java_property(pd)
      JavaProperty.new(pd, self)
    end
          
    # Augments +Jinx::Propertied#default_mandatory_local_attributes to exclude auto-generated properties.
    def default_mandatory_local_attributes
      super.delete_if { |ma| property(ma).autogenerated? }
    end
  end
end
